#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/fl_ask.H>
#include <FL/Fl_Value_Slider.H>
#include <FL/Fl_Gl_Window.H>
#include <FL/gl.h>
#include <GL/glu.h>
#include <math.h>


#include "MyGlWindow.h"

//
// OpenGL App With FLTK Widgets
// erco 11/08/06
//
#define GL_WINDOW_BOUNDS 16
#define BUFSIZE 256

//OpenGl object name ids....
#define ID_NOTHING          0
#define ID_START_MARKER     1
#define ID_END_MARKER       2
#define ID_NOTES_START      10


// OPENGL WINDOW CONSTRUCTOR
MyGlWindow::MyGlWindow(int X,int Y,int W,int H,const char*L) : Fl_Gl_Window(X,Y,W,H,L)
{
    sprintf(messageString, "");

    playCursor = 0.0;
    glObjectIdIndex = ID_NOTES_START;

    setPlayCursor(0);
    end();
}



static void Timer_CB(void *userdata)
{
        MyGlWindow *o = (MyGlWindow*)userdata;
        o->damage(1);

        Fl::remove_timeout( Timer_CB );
        Fl::add_timeout(0.01, Timer_CB, userdata);
}


void MyGlWindow::setPlayCursor(double p)
{
    playCursor = p;
    Timer_CB( (void*)this);
}


// FIX OPENGL VIEWPORT
//     Do this on init or when window's size is changed
void MyGlWindow::FixViewport(int W,int H)
{
    glLoadIdentity();
    glViewport(0,0,W,H);
    setCamera();
    setupGrid();
}

void MyGlWindow::setupGrid()
{
    xGridSpacing = 1;
    while ((rightCamera - leftCamera)/xGridSpacing > 40)
    {
        xGridSpacing *= 2;
    }
   xGridStart = floor(leftCamera) - ((int)(floor(leftCamera))%xGridSpacing);
}


void MyGlWindow::drawInteractiveObjects()
{
    vector<NoteBox> * pNotes = &(pSound->notes);
    //draw notes
    glEnable(GL_BLEND);     // Turn blending On
    glBlendFunc(GL_SRC_ALPHA, GL_ONE);
    for (int i = 0; i < pNotes->size(); i++)
        (*pNotes)[i].draw();
    glDisable(GL_BLEND);     // Turn blending Off

    //draw loop start marker
    glLoadName(ID_START_MARKER);
    glBegin(GL_LINE_STRIP);
        glColor3f(1, 0, 0);
        glVertex3f(pSound->startMarker, -1000, 0);
        glVertex3f(pSound->startMarker, 1000, 0);
    glEnd();

    //draw loop end marker
    glLoadName(ID_END_MARKER);
    glLineWidth(2);
    glBegin(GL_LINE_STRIP);
        glColor3f(0, 0, 1);
        glVertex3f(pSound->endMarker, -1000, 0);
        glVertex3f(pSound->endMarker, 1000, 0);
    glEnd();

}


// DRAW METHOD
void MyGlWindow::draw()
{
    vector<NoteBox> * pNotes = &(pSound->notes);

    if (!valid()) { valid(1); FixViewport(w(), h()); }      // first time? init
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);      // clear the color and depth buffer
    glMatrixMode (GL_PROJECTION);
    glLoadIdentity ();
    setCamera();
    glMatrixMode (GL_MODELVIEW);
    glLoadIdentity ();


    //draw grid
    glLoadName(ID_NOTHING);

    //int xGridSpacing = pow(2, floor((((rightCamera - leftCamera) / 16))) );
    for (double x=xGridStart; x<floor(rightCamera); x+=xGridSpacing)
    {
        glBegin(GL_LINE_STRIP);
            glColor3f(0.15, 0.15, 0.15);
            glVertex3f(x, -1000, 0);
            glVertex3f(x, 1000, 0);
        glEnd();
    }

    for (int y=(int)floor(bottomCamera); y<(int)floor(topCamera); y++)
    {
        glBegin(GL_LINE_STRIP);
            glColor3f(0.15, 0.15, 0.15);
            glVertex3f(-1000, y, 0);
            glVertex3f(1000, y, 0);
        glEnd();
    }


    drawInteractiveObjects();

    env.draw();

    //draw play cursor
    glLoadName(ID_NOTHING);
    glLineWidth(1);
    glBegin(GL_LINE_STRIP);
        glColor3f(0, 1, 0);
        glVertex3f(playCursor, -1000, 0);
        glVertex3f(playCursor, 1000, 0);
    glEnd();

    //draw message string
    gl_font(FL_COURIER, 12);
    gl_draw(messageString, 0, 11);



}


// HANDLE WINDOW RESIZING
void MyGlWindow::resize(int X,int Y,int W,int H)
{
    Fl_Gl_Window::resize(X,Y,W,H);
    FixViewport(W,H);
    redraw();
}




void MyGlWindow::setCamera()
{
    glOrtho(leftCamera,rightCamera,bottomCamera,topCamera,-1,1);
}




int MyGlWindow::getNoteIdFromGlHitId(int glHitId)
{
    vector<NoteBox> * pNotes = &(pSound->notes);
    bool found = false;
    int i;
    for (i=0; i<pNotes->size(); i++)
    {
        if ((*pNotes)[i].id == glHitId)
        {
            found = true;
            break;
        }
    }

    if (found)
        return i;
    else
        return -1;
}




void MyGlWindow::processHits(int event, int button, int x, int y, GLint hits, GLuint buffer[])
/* this is the same "boiler plate" as before */
{
   //fl_alert("dd");
    vector<NoteBox> * pNotes = &(pSound->notes);
    if (hits == 0) return;

    int noteId = -1;
    unsigned int i, j;
    GLuint names, *ptr;
    float z1, z2;
    ptr = (GLuint *) buffer;
    int firstId = *ptr;
    int lastId = -1;
    for (i = 0; i < hits; i++)
    { /* for each hit */
        names = *ptr; ptr++;//how many names
        ptr++;//z1
        ptr++;//z2
        //firstId = *ptr;
        for (j = 0; j < names; j++)
        {  /* for each name */
            int tempNoteId = getNoteIdFromGlHitId(*ptr);
            if (tempNoteId > -1)
                noteId = tempNoteId;
          //fl_alert("name  = %d ", *ptr);
          lastId = *ptr;
          ptr++;
        }
    }
   // fl_alert("firstId = %d", firstId);
   // fl_alert("noteId = %d", noteId);
   // fl_alert("lastId = %d", lastId);
    double xGl,yGl,zGl;
    winXYtoGlXY(x, y, 0, &xGl, &yGl, &zGl);

    dragGlObjId = lastId;

    if (lastId >= ID_NOTES_START)
    {
        //process the note event and then maybe delete it
        bool bDeleteIt = (*pNotes)[ noteId ].event( event, button, xGl, yGl, lastId ); //lastId is the actual glObj id that was hit, noteId is the index of the note
        if (bDeleteIt) pNotes->erase(pNotes->begin() + noteId);
        //(*pNotes)[ getNoteIdFromGlHitId(*ptr) ].toggle();
        dragNoteId = noteId;
    }
    else if (lastId == ID_END_MARKER)
    {

    }
}




void MyGlWindow::pickRects(int event, int button, int x, int y)
{
    vector<NoteBox> * pNotes = &(pSound->notes);
    //fl_alert("x =  \'%d\' found!", x);
    GLuint selectBuf[BUFSIZE];
    GLint hits;
    GLint viewport[4];
    //if (button != GLUT_LEFT_BUTTON || state != GLUT_DOWN)
    //return;
    glGetIntegerv(GL_VIEWPORT, viewport);
    glSelectBuffer(BUFSIZE, selectBuf);
    glRenderMode(GL_SELECT);
    glInitNames();
    glPushName(0);

    glMatrixMode(GL_PROJECTION);

    glPushMatrix();
    glLoadIdentity();

    gluPickMatrix((GLdouble) x, (GLdouble) (viewport[3]-y),    3.0, 3.0, viewport);
    //gluPerspective (40.0, 4.0/3.0, 1.0, 100.0);
    setCamera();

    drawInteractiveObjects();

    glPopMatrix();
    glFlush();
    hits = glRenderMode(GL_RENDER);
    processHits(event, button, x, y, hits, selectBuf);
    redraw();
}



void MyGlWindow::winXYtoGlXY(int x, int y, int z, double *xGl, double *yGl, double *zGl)
{
    //double xGl,yGl,zGl;
    GLdouble modelMatrix[16];
    glGetDoublev(GL_MODELVIEW_MATRIX,modelMatrix);
    GLdouble projMatrix[16];
    glGetDoublev(GL_PROJECTION_MATRIX,projMatrix);
    int viewport[4];
    glGetIntegerv(GL_VIEWPORT,viewport);
    gluUnProject(
        x,
        h() - y,
        z,
        modelMatrix,
        projMatrix,
        viewport,
        //the next 3 parameters are the pointers to the final object
        //coordinates. Notice that these MUST be double's
        xGl, //-&gt; pointer to your own position (optional)
        yGl, // id
        zGl // id
        );
      //coords of the object are now saved in position.
}




void MyGlWindow::newNote(int x, int y)
{
    vector<NoteBox> * pNotes = &(pSound->notes);
    double xGl,yGl,zGl;
    winXYtoGlXY(x, y, 0, &xGl, &yGl, &zGl);
    NoteBox newNote = NoteBox(&glObjectIdIndex);
    newNote.x = (GLint)xGl;
    newNote.x =(int)newNote.x - (int)newNote.x % xGridSpacing;

    newNote.y = (GLint)yGl;
    pNotes->push_back(newNote);
}



int MyGlWindow::handleMouse(int event, int button, int x, int y)
{
    vector<NoteBox> * pNotes = &(pSound->notes);
    int ret = 0;
    double xGl,yGl,zGl;
    winXYtoGlXY(x, y, 0, &xGl, &yGl, &zGl);

    if ( event == FL_PUSH )
    {
        if (Fl::event_ctrl())
                newNote(Fl::event_x() , Fl::event_y());
        pickRects(event, button, Fl::event_x() , Fl::event_y());
        ret = 1;
    }

    if (event == FL_DRAG)
    {
        if (dragNoteId >= 0 && dragNoteId < pNotes->size())
        {
            (*pNotes)[dragNoteId].event(event, button, xGl, yGl);
            (*pNotes)[dragNoteId].x =(int)(*pNotes)[dragNoteId].x - (int)(*pNotes)[dragNoteId].x % xGridSpacing;
            redraw();
        }
        else
        switch (dragGlObjId)
        {

        case ID_END_MARKER:
            if (xGl < pSound->startMarker) xGl = pSound->startMarker + 1;
            pSound->endMarker = (long)xGl - (int)xGl%xGridSpacing;
            break;
        case ID_START_MARKER:
            if (xGl > pSound->endMarker) xGl = pSound->endMarker - 1;
            if (xGl <  0) xGl = 0;
            pSound->startMarker = (long)xGl - (int)xGl%xGridSpacing;
        }

        ret = 1;
    }
    if (event == FL_RELEASE )
    {
        dragGlObjId = -1;
        dragNoteId = -1;
        ret = 1;
    }
/*
    switch ( button ) {
    case 1: // LMB
        ret = 1;
        // Based on the action, print the action and
        // coordinates where it occurred.
        if ( event == FL_PUSH )
        {
             if (Fl::event_ctrl())
                newNote(Fl::event_x() , Fl::event_y());
             pickRects(event, button, Fl::event_x() , Fl::event_y());
        }
        else if (event == FL_DRAG )
        {
            if (dragNoteId >= 0 && dragNoteId < pNotes->size())
            {
                (*pNotes)[dragNoteId].event(event, button, xGl, yGl);
                redraw();
            }
                        //sprintf(foo,"LMB Drag ( %d , %d )",x,y);
                        //label(foo);
                        //damage(1);
        }
        else if ( event == FL_RELEASE ) {
            dragNoteId = -1;
                        //sprintf(foo,"LMB Release ( %d , %d )",x,y);
                        //label(foo);
                        //damage(1);
        }

        break;

    case 2: // MMB
        ret = 1;
        // Based on the action, print the action and
        // coordinates where it occurred.
        if ( event == FL_PUSH ) {
                        //sprintf(foo,"MMB Push ( %d , %d )",x,y);
                        //label(foo);
                        //damage(1);
        }
        else if (event == FL_DRAG ) {
                        //sprintf(foo,"MMB Drag ( %d , %d )",x,y);
                        //label(foo);
                        //damage(1);
        }
        else if ( event == FL_RELEASE ) {
                        //sprintf(foo,"MMB Release ( %d , %d )",x,y);
                        //label(foo);
                        //damage(1);
        }
        break;
    case 3: // RMB
        ret = 1;
        // Based on the action, print the action and
        // coordinates where it occurred.
        if ( event == FL_PUSH ) {
                        //sprintf(foo,"RMB Push ( %d , %d )",x,y);
                        //label(foo);
                        //damage(1);
        }
        else if (event == FL_DRAG ) {
                        //sprintf(foo,"RMB Drag ( %d , %d )",x,y);
                        //label(foo);
                        //damage(1);
        }
        else if ( event == FL_RELEASE ) {
                        //sprintf(foo,"RMB Release ( %d , %d )",x,y);
                        //label(foo);
                        //damage(1);
        }
        break;
    }*/
    return ret;
}

int MyGlWindow::handle(int event)
{
   switch (event)
   {
        case FL_PUSH:
        case FL_RELEASE:
        case FL_DRAG:
        case FL_MOVE:
            return handleMouse(event,Fl::event_button(), Fl::event_x(),Fl::event_y());
       // case FL_MOUSEWHEEL:
        //    Fl::event_dy()
        //    return 1;

        case FL_FOCUS:
        case FL_UNFOCUS:
            return 1;
     /* case FL_PUSH:
         if (Fl::event_ctrl())
            newNote(Fl::event_x() , Fl::event_y());
         pickRects(1,1,Fl::event_x() , Fl::event_y());
         return 1;*/
     /* case FL_KEYBOARD:
         int key = Fl::event_key();
         switch (key)
         {
            case FL_Left:
               rotation -= rotationIncrement;
               redraw();
               return 1;

            case FL_Right:
               rotation += rotationIncrement;
               redraw();
               return 1;

            case ' ':
               animating = !animating;
               return 1;
         }*/
   }

   return Fl_Gl_Window::handle(event);
}
